﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sudoku
{
	public class SudokuGame
	{
		private SudokuGroup[] m_Rows;
		private SudokuGroup[] m_Columns;
		private SudokuGroup[] m_Squares;

		private int m_SquareSize;
		private static ulong[] m_AllChoicesBySize;

		static SudokuGame()
		{
			m_AllChoicesBySize = new ulong[64 + 1];
			ulong allChoices = 0;
			for (int c = 0; c < 64; c++)
			{
				allChoices = 1u | allChoices << 1;
				m_AllChoicesBySize[c + 1] = allChoices;
			}
		}

		private SudokuGame(int size)
		{
			int sq = (int)Math.Sqrt(size);
			if(sq * sq != size)
				throw new Exception("sqrt of size must be integer.");
			m_SquareSize= sq;

			m_Rows = new SudokuGroup[size];
			m_Columns = new SudokuGroup[size];
			m_Squares = new SudokuGroup[size];

			for (int c = 0; c < size; c++)
			{
				m_Rows[c] = new SudokuGroup(size);
				m_Columns[c] = new SudokuGroup(size);
				m_Squares[c] = new SudokuGroup(size);
			}


			for (int x = 0; x < size; x++)
			{
				for (int y = 0; y < size; y++)
				{
					SudokuValue value = new SudokuValue();
					m_Rows[y][x] = value;
					m_Columns[x][y] = value;
					int sqIndex = (x / sq) + sq * (y / sq);
					int sqX = x % sq;
					int sqY = y % sq;
					int sqInner = sqX + sqY * sq;
					m_Squares[sqIndex][sqInner] = value;
				}
			}
		}

		public SudokuGame() : this(9) { }

		public int Size { get { return m_Rows.Length; } }
		public SudokuValue this[int x, int y] { get { return m_Columns[x][y]; } }

		private SudokuGroup GetSquareOfValue(int x, int y)
		{
			int sqIndex = (x / m_SquareSize) + m_SquareSize * (y / m_SquareSize);
			return m_Squares[sqIndex];
		}

		public ulong GetAvailableValues(int x, int y)
		{
			SudokuValue value = this[x, y];
			ulong result = 0x00;
			if (value.IsSet)
				return result;
			for (int c = 0; c < Size; c++)
			{
				SudokuValue colValue = m_Columns[x][c];
				SudokuValue rowValue = m_Rows[y][c];
				SudokuValue squValue = GetSquareOfValue(x, y)[c];
				if (colValue != value && colValue.IsSet)
					result |= 1u << (colValue.Value - 1);
				if (rowValue != value && rowValue.IsSet)
					result |= 1u << (rowValue.Value - 1);
				if (squValue != value && squValue.IsSet)
					result |= 1u << (squValue.Value - 1);
			}
			return m_AllChoicesBySize[Size] & (~result);
		}

		public static List<int> GetValueList(ulong values)
		{
			int value = 1;
			List<int> list = new List<int>();
			while (values > 0)
			{
				if ((values & 1) != 0)
					list.Add(value);
				value++;
				values = values >> 1;
			}
			return list;
		}

		public void Load(string gameValues)
		{
			for (int y = 0; y < Size; y++)
			{
				for (int x = 0; x < Size; x++)
				{
					int index = x + y * Size;
					char val = gameValues[index];
					if (char.IsDigit(val))
						this[x, y].Value = byte.Parse(val.ToString());
				}
			}
		}


		public override string ToString()
		{
			StringBuilder builder = new StringBuilder();
			for (int y = 0; y < Size; y++)
			{
				for (int x = 0; x < Size; x++)
					builder.Append("-+");
				builder.AppendLine();
				for (int x = 0; x < Size; x++)
				{
					SudokuValue v = this[x, y];
					if(v.IsSet)
						builder.Append(v.Value);
					else
						builder.Append(" ");
					builder.Append('|');
				}
				builder.AppendLine();
			}
			for (int x = 0; x < Size; x++)
				builder.Append("-+");
			builder.AppendLine();
			return builder.ToString();
		}
	}
}
